package com.svndiffdoc;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.tmatesoft.svn.core.wc.SVNDiffStatus;

import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;

public class HTMLReport {
    private static final String TEMPLATE_DIR = "templates";
    private final Configuration freemarkerConfiguration;
    private final Map<String, String> reportParams;
    private final File reportDir;

    private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public HTMLReport(Map<String, String> reportParams) {
        this.freemarkerConfiguration = new Configuration();
        this.freemarkerConfiguration.setTemplateLoader(new ClassTemplateLoader(com.svndiffdoc.HTMLReport.class, "/" + TEMPLATE_DIR));
        this.freemarkerConfiguration.setObjectWrapper(new DefaultObjectWrapper());

        this.reportParams = reportParams;

        this.reportDir = new File("site");
        this.reportDir.mkdirs();
    }

    public void generateIndexHtml() throws Exception {
        File reportFile = new File(reportDir, "index.html");
        Map<Object, Object> dataModel = new HashMap<Object, Object>();
        dataModel.putAll(reportParams);
        generateFile("index.ftl", reportFile, dataModel);
    }

    public void generateStaticFiles() throws Exception {
        generateStaticFile("stylesheet.css");
    }

    public void generateReportSummary() throws Exception {
        File reportFile = new File(reportDir, "summary.html");

        Map<Object, Object> dataModel = new HashMap<Object, Object>();
        dataModel.putAll(reportParams);

        generateFile("summary.ftl", reportFile, dataModel);
    }

    public void generateDirSummary(String dir) throws Exception {
        File tmpDir = new File(reportDir, dir);
        tmpDir.mkdirs();
        File reportFile = new File(tmpDir, "dir-summary.html");

        String stylesheetPath = "stylesheet.css";
        for (int i = 1; i < getFileDeepLevel(reportFile); i++) {
            stylesheetPath = "../" + stylesheetPath;
        }

        Map<Object, Object> dataModel = new HashMap<Object, Object>();
        dataModel.putAll(reportParams);
        dataModel.put("stylesheetPath", stylesheetPath);
        dataModel.put("dir", dir);

        generateFile("dir-summary.ftl", reportFile, dataModel);
    }

    public void generateAllDirsList(Map<String, List<String>> dirFileMap) throws Exception {
        File reportFile = new File(reportDir, "all-dirs.html");

        List<String> dirList = new ArrayList<String>();
        for (String dir : dirFileMap.keySet()) {
            if (!dirFileMap.get(dir).isEmpty()) {
                File tmpFile = new File(dir);
                StringBuffer html = new StringBuffer();
                html.append("<tr>")
                .append("<td nowrap=\"nowrap\">")
                .append("<a href=\"" + dir + (dir.isEmpty() ? "" : "/") + "dir-summary.html" + "\" onclick='parent.fileList.location.href=\"" + dir + (dir.isEmpty() ? "" : "/") + "dir-files.html\"' target=\"summary\">/" + tmpFile.getPath().replace("\\", "/") + "</a>")
                .append("</td>")
                .append("</tr>")
                .append("\n");
                dirList.add(html.toString());
            }
        }

        Map<Object, Object> dataModel = new HashMap<Object, Object>();
        dataModel.putAll(reportParams);
        dataModel.put("dirList", dirList);

        generateFile("all-dirs.ftl", reportFile, dataModel);
    }

    public void generateAllFilesList(Map<String, List<String>> dirFileMap) throws Exception {
        File reportFile = new File(reportDir, "all-files.html");

        List<String> fileList = new ArrayList<String>();
        for (String dir : dirFileMap.keySet()) {
            if ((dirFileMap.get(dir) != null) && !dirFileMap.get(dir).isEmpty()) {
                for (String filePath : dirFileMap.get(dir)) {
                    File tmpFile = new File(filePath);
                    StringBuffer html = new StringBuffer();
                    html.append("<tr>")
                    .append("<td nowrap=\"nowrap\">")
                    .append("<a target=\"summary\" href=\"" + dir + (dir.isEmpty() ? "" : "/") + "file-"+ tmpFile.getName() + ".html" + "\">" + tmpFile.getName() + "</a>")
                    .append("</td>")
                    .append("</tr>")
                    .append("\n");
                    fileList.add(html.toString());
                }
            }
        }

        Map<Object, Object> dataModel = new HashMap<Object, Object>();
        dataModel.putAll(reportParams);
        dataModel.put("fileList", fileList);

        generateFile("all-files.ftl", reportFile, dataModel);
    }

    public void generateDirFilesList(String dir, List<String> filePaths) throws Exception {
        File tmpDir = new File(reportDir, dir);
        tmpDir.mkdirs();
        File reportFile = new File(tmpDir, "dir-files.html");

        List<String> fileList = new ArrayList<String>();
        for (String filePath : filePaths) {
            File tmpFile = new File(filePath);
            StringBuffer html = new StringBuffer();
            html.append("<tr>")
            .append("<td nowrap=\"nowrap\">")
            .append("<a target=\"summary\" href=\"" + "file-" + tmpFile.getName() + ".html" + "\">" + tmpFile.getName() + "</a>")
            .append("</td>")
            .append("</tr>")
            .append("\n");
            fileList.add(html.toString());
        }

        Map<Object, Object> dataModel = new HashMap<Object, Object>();
        dataModel.putAll(reportParams);
        dataModel.put("fileList", fileList);
        dataModel.put("dir", dir);

        generateFile("dir-files.ftl", reportFile, dataModel);
    }

    public void generateFileDiffSummary(SVNDiffStatus differedObject, DiffView diffView) throws Exception {
        File tmpFile = new File(reportDir, differedObject.getPath());
        File parentDir = tmpFile.getParentFile();
        if (parentDir == null) {
            parentDir = reportDir;
        }
        parentDir.mkdirs();
        File reportFile = new File(parentDir, "file-" + tmpFile.getName() + ".html");

        String stylesheetPath = "stylesheet.css";
        for (int i=1; i < getFileDeepLevel(reportFile); i++) {
            stylesheetPath = "../" + stylesheetPath;
        }

        List<String> leftFile  = generateFileSummarySideView(diffView.getOldFile());
        List<String> rightFile = generateFileSummarySideView(diffView.getNewFile());

        Map<Object, Object> dataModel = new HashMap<Object, Object>();
        dataModel.putAll(reportParams);
        dataModel.put("stylesheetPath", stylesheetPath);
        dataModel.put("filePath", differedObject.getPath());
        dataModel.put("leftFile", leftFile);
        dataModel.put("rightFile", rightFile);

        generateFile("file-summary.ftl", reportFile, dataModel);
    }

    private int getFileDeepLevel(File file) {
        int level = 0;
        while ((file = file.getParentFile()) != null) {
            level++;
        }
        return level;
    }

    private List<String> generateFileSummarySideView(List<AnnotatedFileLine> lines) {
        boolean isInsideBorder = false;
        int lineCount = lines.size();
        List<String> htmlLines = new ArrayList<String>();
        for (int i = 0; i < lineCount; i++) {
            AnnotatedFileLine line = lines.get(i);

            String trCssClass = "";
            if (!isInsideBorder && !isMatchPoint(line)) {
                trCssClass += " topBorder";
                isInsideBorder = true;
            }
            if ((isInsideBorder && (((i + 1) < lineCount) && isMatchPoint(lines.get(i + 1)))) || (i == (lineCount - 1))) {
                trCssClass += " bottomBorder";
                isInsideBorder = false;
            }

            StringBuffer htmlLine = new StringBuffer();
            htmlLine.append("<tr class=\"" + trCssClass+ "\">")
            .append("<td>" + getLineNumber(line) + "</td>")
            .append("<td>&nbsp;</td>")
            .append("<td class=\"" + getLineCssClass(line) + "\" title=\"" + getTooltip(line) + "\">" + getLine(line) + "</td>")
            .append("</tr>")
            .append("\n");
            htmlLines.add(htmlLine.toString());
        }
        return htmlLines;
    }

    private boolean isMatchPoint(AnnotatedFileLine line) {
        return (line == null) ? false : line.isMatchPoint();
    }

    private String getLineNumber(AnnotatedFileLine line) {
        return (line == null) ? "" : ("" + (line.getLineNumber() + 1));
    }

    private String getLineCssClass(AnnotatedFileLine line) {
        return (line == null) ? "noline" : line.getLineClass();
    }

    private String getTooltip(AnnotatedFileLine line) {
        return (line == null) ? "" : escapeHtml("Date: " + dateFormat.format(line.getDate()) + ", Rev: " + line.getRevision() + ", Author: " + line.getAuthor() + ", Message: " + line.getMessageLog());
    }

    private String getLine(AnnotatedFileLine line) {
        if (line == null) {
            return "<b></b>";
        }
        if (line.getLineFragments().isEmpty()) {
            return "<b>" + escapeHtml(line.getLine()) + "</b>";
        }
        else {
            String htmlLine = "";
            for (AnnotatedFileLineFragment lineFragment : line.getLineFragments()) {
                htmlLine += "<b class=\"" + lineFragment.getFragmentClass() + "\">" + escapeHtml(lineFragment.getFragment()) + "</b>";
            }
            return htmlLine;
        }
    }

    private String escapeHtml(final String line) {
        return (line == null) ? "" : line.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\t", "        ");
    }

    private void generateFile(String templateFile, File outputFile, Map<Object, Object> dataModel) throws Exception {
        Template template = freemarkerConfiguration.getTemplate(templateFile);
        Writer out = new OutputStreamWriter(new FileOutputStream(outputFile));
        template.process(dataModel, out);
        out.flush();
    }

    private void generateStaticFile(String fileName) throws Exception {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(TEMPLATE_DIR + "/" + fileName);
        FileOutputStream outputStream = new FileOutputStream(new File(reportDir, fileName));

        int read;
        byte[] buffor = new byte[1024];
        while ((read = inputStream.read(buffor)) != -1) {
            outputStream.write(buffor, 0 , read);
        }

        outputStream.flush();
        outputStream.close();
    }

}
